package com.naiterui.common.repository.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.interceptor.TransactionInterceptor;

@Configuration
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "false")
@ConditionalOnSingleCandidate(HibernateAutoProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
public class HibernateAutoConfiguration {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private HibernateAutoProperties hibernateAutoProperties;

    @Bean
    public LocalSessionFactoryBean createSessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(this.dataSource);
        sessionFactory.setPackagesToScan(this.hibernateAutoProperties.getPackagesToScan());
        Properties hibernateProperties = new Properties();
        hibernateProperties.put("hibernate.hbm2ddl.auto", "validate");
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getDialect())) {
            hibernateProperties.put("hibernate.dialect", this.hibernateAutoProperties.getDialect());
        }
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getShowSql())) {
            hibernateProperties.put("hibernate.show_sql", this.hibernateAutoProperties.getShowSql());
        }
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getFormatSql())) {
            hibernateProperties.put("hibernate.format_sql", this.hibernateAutoProperties.getFormatSql());
        }
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getCurrentSessionContextClass())) {
            hibernateProperties.put("hibernate.current_session_context_class", this.hibernateAutoProperties.getCurrentSessionContextClass());
        }
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getIdNewGeneratorMappings())) {
            hibernateProperties.put("hibernate.id.new_generator_mappings", this.hibernateAutoProperties.getIdNewGeneratorMappings());
        }
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getIdOptimizer())) {
            hibernateProperties.put("hibernate.id.optimizer.pooled.preferred", this.hibernateAutoProperties.getIdOptimizer());
        }
        if (StringUtils.isNotEmpty(this.hibernateAutoProperties.getJdbcBatchSize())) {
            hibernateProperties.put("hibernate.jdbc.batch_size", this.hibernateAutoProperties.getJdbcBatchSize());
        }
        sessionFactory.setHibernateProperties(hibernateProperties);
        return sessionFactory;
    }

    @Bean("transactionManager")
    public HibernateTransactionManager createHibernateTransactionManager() {
        HibernateTransactionManager htm = new HibernateTransactionManager();
        htm.setSessionFactory(this.createSessionFactory().getObject());
        return htm;
    }

    @Bean
    public HibernateTemplate createHibernateTemplate() {
        return new HibernateTemplate(this.createSessionFactory().getObject());
    }

    @Bean
    public TransactionInterceptor createTransactionInterceptor() {
        Properties properties = new Properties();
        properties.put("add*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("insert*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("save*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("remove*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("update*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("del*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("send*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("notify*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("regist*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("login", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("cancel*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("create*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("lockGet*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("pay*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("create*", "PROPAGATION_REQUIRED, -java.lang.Exception");
        properties.put("get*", "PROPAGATION_REQUIRED, readOnly, -java.lang.Exception");
        properties.put("find*", "PROPAGATION_REQUIRED, readOnly, -java.lang.Exception");
        properties.put("query*", "PROPAGATION_REQUIRED, readOnly, -java.lang.Exception");
        
        return new TransactionInterceptor(this.createHibernateTransactionManager(), properties);
    }

    @Bean
    public DefaultBeanFactoryPointcutAdvisor createTransactionProxyFactoryBean() {
        DefaultBeanFactoryPointcutAdvisor advisor = new DefaultBeanFactoryPointcutAdvisor();
        advisor.setAdvice(this.createTransactionInterceptor());
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(this.hibernateAutoProperties.getTxPointcutExpress());
        advisor.setPointcut(pointcut);
        advisor.setOrder(Ordered.LOWEST_PRECEDENCE);
        return advisor;
    }

    @Bean
    public JdbcTemplate createJdbcTemplate() {
        return new JdbcTemplate(this.dataSource);
    }

}
